package com.yuhuihui.common.utils.http;

import com.yuhuihui.common.domain.result.HttpClientResult;
import com.yuhuihui.common.utils.PropertiesUtil;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;

/**
 * Http工具类
 *
 * @author yuhh
 * @date 2023-05-25 13:57:04
 */
public final class HttpClientUtil {
    
    /** logger */
    private static final Logger logger = LoggerFactory.getLogger(HttpClientUtil.class);

    /**
     * 编码格式。发送编码格式统一用UTF-8
     */
    private static final String ENCODING = "UTF-8";

    /**
     * 设置连接超时时间，单位毫秒。
     */
    private static final int CONNECT_TIMEOUT;

    /**
     * 请求获取数据的超时时间(即响应时间)，单位毫秒。
     */
    private static final int SOCKET_TIMEOUT;

    static {
        String defaultValue = "10000";
        Properties properties = PropertiesUtil.getProperties();
        CONNECT_TIMEOUT = Integer.parseInt(properties.getProperty("connectTimeout", defaultValue));
        SOCKET_TIMEOUT = Integer.parseInt(properties.getProperty("socketTimeout", defaultValue));
    }


    /**
     * 发送get请求，带请求头和请求参数
     *
     * @param httpParameter 发送请求所需参数
     * @return {@link HttpClientResult } 响应结果
     * @throws Exception 例外
     * @author yuhh
     * @date 2023-05-25 13:57:22
     */
    public static HttpClientResult doGet(HttpParameter httpParameter) throws Exception {
        // 创建httpClient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();

        // 创建访问的地址
        URIBuilder uriBuilder = new URIBuilder(httpParameter.getUrl());
        // 设置求情参数
        if (!CollectionUtils.isEmpty(httpParameter.getParams())) {
            httpParameter.getParams().forEach(uriBuilder::setParameter);
        }

        // 创建请求方式为get的http对象
        HttpGet httpGet = new HttpGet(uriBuilder.build());

        // 设置请求头
        packageHeader(httpParameter.getHeaders(), httpGet);

        // 执行请求并获得响应结果
        return getHttpClientResult(httpClient, httpGet);
    }


    /**
     * 发送post请求，带请求头和请求参数
     *
     * @param httpParameter 发送请求所需参数
     * @return {@link HttpClientResult } 响应结果
     * @throws Exception 例外
     * @author yuhh
     * @date 2023-05-25 13:57:28
     */
    public static HttpClientResult doPost(HttpParameter httpParameter) throws Exception {
        // 创建httpClient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();

        if (httpParameter.getInput() != null){
            // 创建访问的地址
            URIBuilder uriBuilder = new URIBuilder(httpParameter.getUrl());
            // 设置求情参数
            if (!CollectionUtils.isEmpty(httpParameter.getParams())) {
                httpParameter.getParams().forEach(uriBuilder::setParameter);
            }

            // 创建请求方式为post的http对象
            HttpPost httpPost = new HttpPost(uriBuilder.build());

            // 设置请求头
            packageHeader(httpParameter.getHeaders(), httpPost);

            // 封装请求输入流
            packageInputStream(httpParameter.getInput(), httpPost);

            // 执行请求并获得响应结果
            return getHttpClientResult(httpClient, httpPost);
        }

        // 创建请求方式为post的http对象
        HttpPost httpPost = new HttpPost(httpParameter.getUrl());

        // 设置请求头
        packageHeader(httpParameter.getHeaders(), httpPost);

        // 封装请求参数
        packageParam(httpParameter.getParams(), httpPost);

        // 执行请求并获得响应结果
        return getHttpClientResult(httpClient, httpPost);
    }


    /**
     * 发送put请求，带请求参数
     *
     * @param httpParameter 发送请求所需参数
     * @return {@link HttpClientResult } 响应结果
     * @throws Exception 例外
     * @author yuhh
     * @date 2023-05-25 13:57:35
     */
    public static HttpClientResult doPut(HttpParameter httpParameter) throws Exception {
        // 创建httpClient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();

        if (httpParameter.getInput() != null){
            // 创建访问的地址
            URIBuilder uriBuilder = new URIBuilder(httpParameter.getUrl());
            // 设置求情参数
            if (!CollectionUtils.isEmpty(httpParameter.getParams())) {
                httpParameter.getParams().forEach(uriBuilder::setParameter);
            }

            // 创建请求方式为post的http对象
            HttpPut httpPost = new HttpPut(uriBuilder.build());

            // 设置请求头
            packageHeader(httpParameter.getHeaders(), httpPost);

            // 封装请求输入流
            packageInputStream(httpParameter.getInput(), httpPost);

            // 执行请求并获得响应结果
            return getHttpClientResult(httpClient, httpPost);
        }

        // 创建请求方式为put的http对象
        HttpPut httpPut = new HttpPut(httpParameter.getUrl());

        // 设置请求头
        packageHeader(httpParameter.getHeaders(), httpPut);

        // 封装请求参数
        packageParam(httpParameter.getParams(), httpPut);

        // 执行请求并获得响应结果
        return getHttpClientResult(httpClient, httpPut);
    }

    /**
     * 发送delete请求，带请求参数
     *
     * @param httpParameter 发送请求所需参数
     * @return {@link HttpClientResult } 响应结果
     * @throws Exception 异常
     * @author yuhh
     * @date 2023-05-25 13:58:33
     */
    public static HttpClientResult doDelete(HttpParameter httpParameter) throws Exception {
        if (CollectionUtils.isEmpty(httpParameter.getParams())) {
            httpParameter.setParams(new HashMap<>(1));
        }

        httpParameter.getParams().put("_method", "delete");

        return doPost(httpParameter);
    }

    /**
     * 读取httpBody内容
     *
     * @param request request
     * @return {@link String } httpBody内容
     * @throws IOException IOException
     * @author yuhh
     * @date 2023-05-25 13:58:40
     */
    public static String getRequestBody(HttpServletRequest request) throws IOException {
        StringBuilder httpBody = new StringBuilder();
        BufferedReader br = null;
        try {
            br = new BufferedReader(new InputStreamReader(request.getInputStream()));
            String line;
            while ((line = br.readLine()) != null) {
                httpBody.append(line);
            }
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    logger.error("关闭输入流出现异常", e);
                }
            }
        }
        return httpBody.toString();
    }

    /**
     * 封装请求头
     *
     * @param params     请求参数
     * @param httpMethod 请求方式
     * @author yuhh
     * @date 2023-05-25 13:58:53
     */
    private static void packageHeader(Map<String, String> params, HttpRequestBase httpMethod) {
        // 封装请求头
        if (!CollectionUtils.isEmpty(params)) {
            params.forEach(httpMethod::setHeader);
        }
    }

    /**
     * 封装请求参数
     *
     * @param params     请求参数集合
     * @param httpMethod 请求方式
     * @throws Exception 异常
     * @author yuhh
     * @date 2023-05-25 13:58:56
     */
    private static void packageParam(Map<String, String> params, HttpEntityEnclosingRequestBase httpMethod) throws Exception {
        // 封装请求参数
        if (!CollectionUtils.isEmpty(params)) {
            List<NameValuePair> nameValuePairs = params.entrySet().stream()
                    .map(e -> new BasicNameValuePair(e.getKey(), e.getValue()))
                    .collect(Collectors.toList());

            // 设置到请求的http对象中
            httpMethod.setEntity(new UrlEncodedFormEntity(nameValuePairs, ENCODING));
        }
    }

    /**
     * 封装请求文件流
     *
     * @param input      请求文件流
     * @param httpMethod 请求方式
     * @author yuhh
     * @date 2023-05-25 13:59:01
     */
    private static void packageInputStream(InputStream input, HttpEntityEnclosingRequestBase httpMethod) {
        if (input != null){
            httpMethod.setEntity(new InputStreamEntity(input));
        }
    }

    /**
     * 获得响应结果
     *
     * @param httpClient httpClient
     * @param httpMethod 请求方式
     * @return {@link HttpClientResult } 响应结果
     * @throws Exception 异常
     * @author yuhh
     * @date 2023-05-25 13:59:07
     */
    private static HttpClientResult getHttpClientResult(CloseableHttpClient httpClient, HttpRequestBase httpMethod) throws Exception {
        CloseableHttpResponse httpResponse = null;
        /*
         * setConnectTimeout：设置连接超时时间，单位毫秒。
         * setConnectionRequestTimeout：设置从connect Manager(连接池)获取Connection超时时间，单位毫秒。这个属性是新加的属性，因为目前版本是可以共享连接池的。
         * setSocketTimeout：请求获取数据的超时时间(即响应时间)，单位毫秒。 如果访问一个接口，多少时间内无法返回数据，就直接放弃此次调用。
         */
        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build();
        httpMethod.setConfig(requestConfig);
        try {
            // 执行请求
            httpResponse = httpClient.execute(httpMethod);

            // 获取响应结果
            if (httpResponse != null && httpResponse.getStatusLine() != null) {
                String content = "";
                if (httpResponse.getEntity() != null) {
                    content = EntityUtils.toString(httpResponse.getEntity(), ENCODING);
                }
                return new HttpClientResult(httpResponse.getStatusLine().getStatusCode(), content);
            }
            return new HttpClientResult(HttpStatus.SC_INTERNAL_SERVER_ERROR);
        } finally {
            release(httpResponse, httpClient);
        }
    }

    /**
     * 释放资源
     *
     * @param httpResponse httpResponse
     * @param httpClient   httpClient
     * @throws IOException IOException
     * @author yuhh
     * @date 2023-05-25 13:59:17
     */
    private static void release(CloseableHttpResponse httpResponse, CloseableHttpClient httpClient) throws IOException {
        if (httpResponse != null) {
            httpResponse.close();
        }
        if (httpClient != null) {
            httpClient.close();
        }
    }
}
